#include "tdl.h"

TDL::TDL()
{
    _unparsedPage = "";
    _parsedPage = _unparsedPage;
    SysInit();
}

TDL::TDL(QString page)
{
    _unparsedPage = page;
    _parsedPage = _unparsedPage;
    SysInit();
}

void TDL::SysInit()
{
    //SYSTEM FUNCTIONS
    QList<FuncParam> params;
    params.push_back(FuncParam("str"));// PRINT prototype func print(str);
    sysFunctions.push_back(SysFunction("print",params));// Writes the given string into the HTML document
}

void TDL::setPage(QString page)
{
    _unparsedPage = page;
    _parsedPage = _unparsedPage;
}

QString TDL::unparsedPage()
{
    return _unparsedPage;
}

QString TDL::parse()
{
    QStringList parts = _parsedPage.split("<|"); // ex html...<p>lorem ipsum <| print("!"); |> jfwgf <| print("fdkmfdk"); |> html...
    if(parts.count() > 1)// That means we MUST have at least one <| in out page, so we need to parse
    {
       for(int i = 0; i < parts.count(); i++)//For each element of the list
       {
            if(parts.at(i).contains("|>"))// Find if it contains |> . If it does, it is sure that if we split on it the first element of the new list will be tdl code :)
            {
                QStringList parseList = parts.at(i).split("|>");
                QString parsed = parseBlock(parseList.at(0));//Parsing...
                parts[i] = parsed+parseList.at(1); // we update the list so at to rebuild it into an html page later
            }
       }
    }
    else // no <|, no parsing, return the page as it was
    {
        return _parsedPage;
    }

    _parsedPage = "";//empty parse page to rebuild it
    for(int i = 0; i < parts.count(); i++)// for every element of the part list ( wich has been changed when parsing...)
    {
        _parsedPage+=parts.at(i); // add it to the html page
    }
    return _parsedPage;// give the html page back to the server :)
}

QString TDL::parseBlock(QString code, bool funcCode)// Here is the basic function for parsing. FuncCode is wether or not the code parsed is in a function (so as to destroy variables at the end and know whether or not to accept the return keyword)
{
    QMap<QString,QString> bckup;
    if(funcCode) // if it is a function code
    {
        bckup = vars;// we store the existing variables in a new map so as to restore them in the end of the code
    }
    bool condition = false;
    bool loop = false;
    bool rtn = false;
    bool codeBlock = false;
    QString funcName = ""; // temporary variable for function names
    bool inQuotes = false;
    QString tempReAlloc = "";
    QString tempVar = ""; // 'temporary' string for variable name
    bool varAlloc = false;
    bool varReAlloc = false;
    bool funcDeclaration = false;
    QString inFunction = "";
    QString container = ""; // container string: this variable will contain the instructions etc... youll see ;)
    QString html = "";// we create a new variable wich will contain the html code (code after parsing)
    for(int i = 0; i < code.length(); i++)// for every character of the unparsed code...
    {
        if(container == "var" && code.at(i) == ' ' && !inQuotes && !codeBlock)//found keyword 'var' and there is a space after it(meaning it is a variable allocation)
        {
            container.clear();
            varAlloc = true;
        }
        else if(container == "return" && !inQuotes && code.at(i) == ' ' && funcCode)
        {
            container.clear();
            rtn = true;
        }
        else if(container == "func" && !inQuotes && !codeBlock && code.at(i) == ' ')
        {
            container.clear();
            funcDeclaration = true;
        }
        else if(code.at(i) == '(' && !inQuotes && !codeBlock) // there is a parenthesis, so its either a function declaration or a function call
        {
            while(container.startsWith(' ') || container.startsWith('\n') || container.startsWith('\r'))
            {
                container = container.right(container.length()-1);
            }
            while(container.endsWith(' '))
            {
                container.chop(1);
            }
            if(!funcDeclaration)// if there is no function declaration
            {
                if(container!="while" && container!="if")
                {
                    for(int j = 0; j < sysFunctions.count(); j++)// search for the function in the system functions
                    {
                       if(sysFunctions.at(j).name() == container)
                       {
                           inFunction = container;// and tell the parser wich function it is
                           container.clear();
                       }
                    }
                    for(int j = 0; j < userFunctions.count(); j++)// search for the function in the user functions
                    {
                        if(userFunctions.at(j).name() == container)
                        {
                            inFunction = container;// and tell the parser wich function it is
                            container.clear();
                        }
                    }
                    if(inFunction.isEmpty())
                    {
                        html+="TDL Error: parenthesis with no good reason.";
                    }
                }
                else if(container=="if")
                {
                    container.clear();
                    condition = true;
                }
                else if(container=="while")
                {
                    container.clear();
                    loop=true;
                }
            }
            else
            {
                bool ok = true;
                for(int j = 0; j < sysFunctions.count(); j++)
                {
                    if(sysFunctions.at(j).name() == container)
                    {
                        ok = false;
                    }
                }
                for(int j = 0; j < userFunctions.count(); j++)
                {
                    if(userFunctions.at(j).name() == container)
                    {
                        ok = false;
                    }
                }

                if(!ok)
                {
                    html+="TDL Error: function "+container+" already exists.";
                }
                else
                {
                    funcName = container;
                    container.clear();
                }
            }
        }
        else if(code.at(i) == ')' && !inQuotes && !codeBlock)
        {
            if(!funcDeclaration && code.at(i+1) == ';')
            {
                if(inFunction == "print")
                {
                    container = calculateExp(container);
                    if(container.startsWith('"') && container.endsWith('"'))
                    {
                        container = container.right(container.length()-1);
                        container = container.left(container.length()-1);
                    }
                    html+=container;
                    container.clear();
                    inFunction="";
                    i++;
                }
                else
                {
                    for(int j = 0; j < userFunctions.count(); j++)
                    {
                        if(userFunctions.at(j).name() == inFunction)
                        {
                            QStringList params = container.split(',');
                            if(params.count() == userFunctions.at(j).params().count())
                            {
                                if(userFunctions.at(j).isRtnFunc() && varAlloc || varReAlloc) // if it is a return function and we are in a variable allocaltion or re-allocation, then we must give the value to the var :)
                                {
                                    QString varDeclars;
                                    for(int u = 0; u < params.count(); u++)
                                    {
                                        if(varExists(userFunctions.at(j).params().at(u).name()))
                                        {
                                            varDeclars+=userFunctions.at(j).params().at(u).name()+" = "+calculateExp(params.at(u))+";";
                                        }
                                        else
                                        {
                                            varDeclars+="var "+userFunctions.at(j).params().at(u).name()+" = "+calculateExp(params.at(u))+";";
                                        }
                                    }
                                    QStringList strlist = parseBlock(varDeclars+userFunctions.at(j).code(),true).split(QString(QChar(1339))+"(((!TDL!)))---"+QString(QChar(1339)));

                                    if(strlist.count() > 1)
                                    {
                                        html+=strlist.at(1);
                                        if(varAlloc)
                                        {
                                            vars.insert(tempVar,strlist.at(0));
                                            varAlloc = false;
                                            tempVar.clear();
                                        }
                                        else if(varReAlloc)
                                        {
                                            vars[tempReAlloc]=strlist.at(0);
                                            varReAlloc = false;
                                            tempReAlloc.clear();
                                        }
                                    }
                                }
                                else if(userFunctions.at(j).isRtnFunc() && !varAlloc && !varReAlloc)
                                {
                                    html+="TDL Error: invalid use of "+userFunctions.at(j).name()+", a return function <strong>must</strong> be used to allocate or re-allocate a variable.";
                                }
                                else if(!userFunctions.at(j).isRtnFunc() && varAlloc || varReAlloc)
                                {
                                    html+="TDL Error: invalid use of "+userFunctions.at(j).name()+", this function does not return a value so it cannot be used to allocate or re-allocate a variable.";
                                }
                                else if(!userFunctions.at(j).isRtnFunc() && !varAlloc && !varReAlloc)
                                {
                                    QString varDeclars;
                                    for(int u = 0; u < params.count(); u++)
                                    {
                                       varDeclars+="var "+userFunctions.at(j).params().at(u).name()+" = "+calculateExp(params.at(u))+";";
                                    }
                                    html+=parseBlock(varDeclars+userFunctions.at(j).code());
                                }
                                inFunction="";
                                i++;
                                container.clear();
                            }
                            else
                            {
                                html+="TDL Error function "+userFunctions.at(j).name()+" doesn't have the number of parameters you pass to it.";
                            }
                        }
                    }
                }
            }
            else if(funcDeclaration)
            {
                QList<FuncParam> params;
                QStringList rawParams = container.split(',');
                for(int i = 0; i < rawParams.count(); i++)
                {
                    params.push_back(FuncParam(rawParams.at(i)));
                }
                userFunctions.push_back(Function(funcName,params));
                container.clear();
            }
        }
        else if(code.at(i) == '{')
        {
            if(!funcDeclaration && !loop && !condition)
            {
                html+="TDL Error: no apparent reason for {";
            }
            else if(loop || condition)
            {
                container+=code.at(i);
                codeBlock = true;
            }
            else
            {
                codeBlock = true;
            }
        }
        else if(code.at(i) == '}')
        {
            if(!codeBlock)
            {
                html+="TDL Error: no apparent reason for }";
            }
            else if(funcDeclaration)
            {
                for(int j = 0; j < userFunctions.count(); j++)
                {
                    if(userFunctions.at(j).name() == funcName)
                    {
                        if(container.contains("return "))
                        {
                            userFunctions[j].setRtnFunction(true);
                        }
                        userFunctions[j].setCode(container);
                        container.clear();
                        funcDeclaration = false;
                        funcName.clear();
                    }
                }
            }
            else if(condition)
            {
                QStringList list = container.split('{');
                if(calculateCondition(list.at(0)))
                {
                    html+=parseBlock(list.at(1));
                }
                condition=false;
                container.clear();
            }
            else if(loop)
            {
                QStringList list = container.split('{');
                while(calculateCondition(list.at(0)))
                {
                    html+=parseBlock(list.at(1));
                }
                loop=false;
                container.clear();
            }
            codeBlock=false;
        }
        else if(code.at(i) == '=' && !inQuotes && !codeBlock && !loop && !condition)
        {
            if(varAlloc==true)
            {
                if(container.endsWith(' '))
                {
                    container.chop(1);
                }
                if(!varExists(container))
                {
                    tempVar = container;
                }
                else
                {
                    html+="<p>TDL Error: variable "+container+" already exist, cannot re-create it. Don't use the var keyword.</p>";
                }
                container.clear();
            }
            else
            {
                if(container.endsWith(' '))
                {
                    container.chop(1);
                }
                if(varExists(container))
                {
                    tempReAlloc = container;
                    container.clear();
                    varReAlloc = true;
                }
            }
        }
        else if(code.at(i) == ';' && !inQuotes)
        {
            if(varAlloc==true)
            {
                if(container.startsWith(' '))
                {
                    container = container.right(container.length()-1);
                }
                vars.insert(tempVar,calculateExp(container));
                varAlloc = false;
                tempVar.clear();
                container.clear();
            }
            else if(varReAlloc==true)
            {
                if(container.startsWith(' '))
                {
                    container = container.right(container.length()-1);
                }
                vars[tempReAlloc]=calculateExp(container);
                varReAlloc = false;
                tempReAlloc.clear();
                container.clear();
            }
            else if(codeBlock)
            {
                container+=code.at(i);
            }
            else if(rtn && funcCode)
            {
                while(container.startsWith(' ') || container.startsWith('\n') || container.startsWith('\r'))
                {
                    container = container.right(container.length()-1);
                }
                while(container.endsWith(' '))
                {
                    container.chop(1);
                }

                html = calculateExp(container)+QChar(1339)+"(((!TDL!)))---"+QChar(1339)+html;
                rtn = false;
            }
        }
        else
        {
            if(!varAlloc && !varReAlloc && !codeBlock && code.at(i) != ' ' && code.at(i) != '\n' && code.at(i) != '\r') // we exclude the space characters to test the instructions with the if, except if we are in quoter
            {
                container+=code.at(i);
            }
            else if(varAlloc || varReAlloc || !inFunction.isEmpty() || codeBlock)
            {
                container+=code.at(i);
            }

            if(code.at(i)=='"')
            {
                inQuotes = !inQuotes;
            }
        }
    }
    if(funcCode)
    {
        vars = bckup;// we restore the variables :)
    }
    return html;
}

QString TDL::calculateExp(QString exp) // Function to calculate expressions (+/-*, etc...)
{
    QString sum;
    QString firstPart;
    QString in="";
    QString container; // same principle as code parsing, was inspired by it :)
    bool inQuotes = false;// so as not to take into account the operators in quotes :)
    for(int i = 0; i < exp.count(); i++)
    {
        if((isOperator(exp.at(i)) || (i == exp.length()-1 && !in.isEmpty())) && !inQuotes || (exp.at(i)=='"' && i== exp.length()-1 && !in.isEmpty()))//if we find an operator, we are not in quotes or we are in the end of line or even in the end of line with a quote
        {
            if(i == exp.length()-1 && !in.isEmpty())// if its last character
            {
                container+=exp.at(i);// add it to container :)
            }

            if(container.startsWith(' '))// we remove the spaces ^^
            {
                container = container.right(container.length()-1);
            }
            if(container.endsWith(' '))
            {
                container = container.left(container.length()-1);
            }

            if(in.isEmpty()) // if no other calculation to be done
            {
                firstPart = container;// the first part is equal to the container :)
                container.clear(); // clear the container
            }
            else// but if we already are in a...
            {
                if(varExists(firstPart))
                {
                    firstPart = getVarValue(firstPart);
                }
                if(varExists(container))
                {
                    container = getVarValue(container);
                }

                if(in == "substraction")//  substraction
                {
                    QVariant v(firstPart);
                    QVariant v2(container);
                    if(((v2.toInt() == 0 && container == "0") || v2.toInt() != 0) && ((v.toInt() == 0 && firstPart == "0") || v.toInt() != 0))// if both elements are numbers
                    {
                        sum=QString::number(v.toInt()-v2.toInt());// substract them and store the result into the sum
                    }
                    else
                    {
                        return "TDL Error: substraction only possible between two or more <strong>numbers</strong>.";
                    }
                }
                else if(in == "addition")// while an addition may seem the simplest calculation, it isn't as we have to take strings into account... i will only allow addition between strings and additions between numbers, not mixed ones
                {
                    QVariant v(firstPart);
                    QVariant v2(container);
                    if(((v2.toInt() == 0 && container == "0") || v2.toInt() != 0) && ((v.toInt() == 0 && firstPart == "0") || v.toInt() != 0))// if both elements are numbers
                    {
                        sum=QString::number(v.toInt()+v2.toInt());// add them to the result
                    }
                    else if(((v2.toInt() == 0 && container == "0") || v2.toInt() != 0) && (v.toInt() == 0 && firstPart != "0"))// first part is'nt a number, but second is?
                    {
                        return "TDL Error: cannot addition a <strong>string</strong> with a <strong>number</strong>."; // that says it all :P
                    }
                    else if(((v.toInt() == 0 && container == "0") || v.toInt() != 0) && (v2.toInt() == 0 && firstPart != "0"))// second part is'nt a number, but first is?
                    {
                        return "TDL Error: cannot addition a <strong>string</strong> with a <strong>number</strong>."; // that says it all :P
                    }
                    else if((v2.toInt() == 0 && container != "0") && (v.toInt() == 0 && firstPart != "0"))// no numbers at all
                    {
                        if(container.startsWith('"') && container.endsWith('"') && firstPart.startsWith('"') && firstPart.endsWith('"'))// check for quotes
                        {
                            firstPart = firstPart.left(firstPart.length()-1);
                            container = container.right(container.length()-1);
                            sum = firstPart+container;// add them to the sum
                        }
                    }
                }
                if(in == "multiplication")//  multiplication
                {
                    QVariant v(firstPart);
                    QVariant v2(container);
                    if(((v2.toInt() == 0 && container == "0") || v2.toInt() != 0) && ((v.toInt() == 0 && firstPart == "0") || v.toInt() != 0))// make sure both element are numbers
                    {
                        sum=QString::number(v.toInt()*v2.toInt());// multiply them ;)
                    }
                    else
                    {
                        return "TDL Error: multiplication only possible between two or more <strong>numbers</strong>.";
                    }
                }
                if(in == "division")//  division, shame as multiplication and substraction ;)
                {
                    QVariant v(firstPart);
                    QVariant v2(container);
                    if(((v2.toInt() == 0 && container == "0") || v2.toInt() != 0) && ((v.toInt() == 0 && firstPart == "0") || v.toInt() != 0))
                    {
                        sum=QString::number(v.toInt()/v2.toInt());// divide them ;)
                    }
                    else
                    {
                        return "TDL Error: division only possible between two or more <strong>numbers</strong>.";
                    }
                }
                firstPart = sum;// the sum becomes the first part xD muhahaha ^^
                container.clear();// clear the container :)
            }

            if(exp.at(i)=='+')
            {
                in="addition";
            }
            else if(exp.at(i)=='-')
            {
                in="substraction";
            }
            else if(exp.at(i)=='*')
            {
                in="multiplication";
            }
            else if(exp.at(i)=='/')
            {
                in="division";
            }
        }
        else// if there current character is not an operation, add it to the container string ^^
        {
            if(exp.at(i)=='"')
            {
                inQuotes = !inQuotes;
            }
            container+=exp.at(i);
        }
    }
    if(in.isEmpty())// if in is empty, meaning there was no calculation to do at all :)
    {
        if(varExists(container))
        {
            container = getVarValue(container);
        }

        if(container.startsWith(' '))// we remove the spaces ^^
        {
            container = container.right(container.length()-1);
        }
        if(container.endsWith(' '))
        {
            container = container.left(container.length()-1);
        }
        if(container.startsWith('"') && container.endsWith('"'))// and the quotes :)
        {
            sum = container;
        }
        else
        {
            QVariant v(container);
            if(v.toInt() == 0 && container != "0")
            {
                return "TDL Error: a string must always have quotes surrounding it.";
            }
            else
            {
                sum = container;
            }
        }
    }
    return sum;
}

bool TDL::calculateCondition(QString exp)// a function to calculate a condition :)
{
    // AND and OR will be supported, so we will split the expression at ANDs, then at ORs, then calculate each splited part and see if it is true. priority: AND->OR meaning if(smth==0 AND smth==1 OR smth==2) = if(smth==0 AND (smth==1 OR smth==2))
    bool rtn = true;
    QStringList andParts = exp.split("AND");
    for(int i = 0; i < andParts.count(); i++)
    {
        QStringList orParts = andParts.at(i).split("OR");
        bool orB = false;
        for(int j = 0; j < orParts.count(); j++)
        {
            bool equalTo = false;
            bool notEqualTo = false;
            bool smallerThan = false;
            bool greaterThan = false;
            QString firstPart;
            QString container;
            for(int u = 0; u < orParts.at(j).length(); u++)
            {
                if(orParts.at(j).at(u) == '=' && orParts.at(j).at(u+1) == '=')
                {
                    u++;
                    equalTo = true;
                    firstPart = calculateExp(container);
                    container.clear();
                }
                else if(orParts.at(j).at(u) == '!' && orParts.at(j).at(u+1) == '=')
                {
                    u++;
                    notEqualTo=true;
                    firstPart = calculateExp(container);
                    container.clear();
                }
                else if(orParts.at(j).at(u) == '<' && orParts.at(j).at(u+1) != '=')
                {
                    smallerThan=true;
                    firstPart = calculateExp(container);
                    container.clear();
                }
                else if(orParts.at(j).at(u) == '<' && orParts.at(j).at(u+1) == '=')
                {
                    u++;
                    equalTo = true;
                    smallerThan=true;
                    firstPart = calculateExp(container);
                    container.clear();
                }
                else if(orParts.at(j).at(u) == '>' && orParts.at(j).at(u+1) != '=')
                {
                    greaterThan=true;
                    firstPart = calculateExp(container);
                    container.clear();
                }
                else if(orParts.at(j).at(u) == '>' && orParts.at(j).at(u+1) == '=')
                {
                    u++;
                    equalTo = true;
                    greaterThan=true;
                    firstPart = calculateExp(container);
                    container.clear();
                }
                else
                {
                    container+=orParts.at(j).at(u);
                }
            }
            container = calculateExp(container);

            if(firstPart==container && equalTo && !smallerThan)
            {
                orB=true;
            }
            else if(firstPart!=container && notEqualTo)
            {
                orB=true;
            }
            else if(smallerThan && !equalTo)
            {
                bool ok1,ok2;
                int num1 = firstPart.toInt(&ok1);
                int num2 = container.toInt(&ok2);
                if(ok1 && ok2)
                {
                    orB = num1<num2;
                }
                else
                {
                    orB = firstPart<container;
                }
            }
            else if(smallerThan && equalTo)
            {
                bool ok1,ok2;
                int num1 = firstPart.toInt(&ok1);
                int num2 = container.toInt(&ok2);
                if(ok1 && ok2)
                {
                    orB = num1<=num2;
                }
                else
                {
                    orB = firstPart<=container;
                }
            }
            else if(greaterThan && !equalTo)
            {
                bool ok1,ok2;
                int num1 = firstPart.toInt(&ok1);
                int num2 = container.toInt(&ok2);
                if(ok1 && ok2)
                {
                    orB = num1>num2;
                }
                else
                {
                    orB = firstPart>container;
                }
            }
            else if(greaterThan && equalTo)
            {
                bool ok1,ok2;
                int num1 = firstPart.toInt(&ok1);
                int num2 = container.toInt(&ok2);
                if(ok1 && ok2)
                {
                    orB = num1>=num2;
                }
                else
                {
                    orB = firstPart>=container;
                }
            }
        }
        if(orB == false)
        {
            rtn=false;
        }
    }
    return rtn;
}

bool TDL::varExists(QString name)
{
    if(vars.contains(name))
    {
        return true;
    }
    return false;
}

bool TDL::isOperator(QChar s)
{
    return (s=='+'||s=='-'||s=='*'||s=='/');
}

QString TDL::getVarValue(QString name)
{
    return vars.value(name);
}
